package com.cmbchina.encrypt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.battcn.boot.extend.configuration.crypto.HexUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.springframework.util.Base64Utils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 招商银行国密 SM2加解密，加签验签
 *
 * @author liuxb
 * @date 2021/11/26 22:51
 */
public class SM2 {
    private static final String CURVE_NAME = "sm2p256v1";
    /**
     * 规范默认, 否则影响验签
     */
    private static final byte[] USER_ID = "1234567812345678".getBytes();

    SM2() {
    }

    public static SM2 getInstance() {
        return new SM2();
    }

    /**
     * 获取椭圆曲线
     */
    private ECDomainParameters getECDomainParameters() {
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec(CURVE_NAME);
        return new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
    }

    private ECCurve getSM2Curve() {
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec(CURVE_NAME);
        return spec.getCurve();
    }

    private ECPublicKeyParameters encodePublicKey(byte[] value) {
        byte[] x = new byte[32];
        byte[] y = new byte[32];
        System.arraycopy(value, 1, x, 0, 32);
        System.arraycopy(value, 33, y, 0, 32);
        BigInteger X = new BigInteger(1, x);
        BigInteger Y = new BigInteger(1, y);
        ECPoint Q = this.getSM2Curve().createPoint(X, Y);
        return new ECPublicKeyParameters(Q, this.getECDomainParameters());
    }

    private ECPrivateKeyParameters encodePrivateKey(byte[] value) {
        BigInteger d = new BigInteger(1, value);
        return new ECPrivateKeyParameters(d, this.getECDomainParameters());
    }

    private byte[] C1C2C3ToC1C3C2(byte[] cipherText) {
        if (cipherText != null && cipherText.length >= 97) {
            byte[] bytes = new byte[cipherText.length];
            System.arraycopy(cipherText, 0, bytes, 0, 65);
            System.arraycopy(cipherText, cipherText.length - 32, bytes, 65, 32);
            System.arraycopy(cipherText, 65, bytes, 97, cipherText.length - 97);
            return bytes;
        } else {
            throw new SMCryptKYException(ErrorCode.E10406);
        }
    }

    private byte[] C1C3C2ToC1C2C3(byte[] cipherText) {
        if (cipherText != null && cipherText.length >= 97) {
            byte[] bytes = new byte[cipherText.length];
            System.arraycopy(cipherText, 0, bytes, 0, 65);
            System.arraycopy(cipherText, 97, bytes, 65, cipherText.length - 97);
            System.arraycopy(cipherText, 65, bytes, cipherText.length - 32, 32);
            return bytes;
        } else {
            throw new SMCryptKYException(ErrorCode.E10406);
        }
    }

    private byte[] format(byte[] value) {
        if (value.length == 32) {
            return value;
        } else {
            byte[] bytes = new byte[32];
            if (value.length > 32) {
                System.arraycopy(value, value.length - 32, bytes, 0, 32);
            } else {
                System.arraycopy(value, 0, bytes, 32 - value.length, value.length);
            }

            return bytes;
        }
    }

    private byte[] encodeDERSignature(byte[] signature) {
        byte[] r = new byte[32];
        byte[] s = new byte[32];
        System.arraycopy(signature, 0, r, 0, 32);
        System.arraycopy(signature, 32, s, 0, 32);
        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(new ASN1Integer(new BigInteger(1, r)));
        vector.add(new ASN1Integer(new BigInteger(1, s)));

        try {
            return (new DERSequence(vector)).getEncoded();
        } catch (IOException e) {
            throw new SMCryptKYException(ErrorCode.E10501, e);
        }
    }


    private byte[] decodeDERSignature(byte[] signature) {
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));

        try {
            ASN1Sequence primitive = (ASN1Sequence) stream.readObject();
            Enumeration enumeration = primitive.getObjects();
            BigInteger R = ((ASN1Integer) enumeration.nextElement()).getValue();
            BigInteger S = ((ASN1Integer) enumeration.nextElement()).getValue();
            byte[] bytes = new byte[64];
            byte[] r = this.format(R.toByteArray());
            byte[] s = this.format(S.toByteArray());
            System.arraycopy(r, 0, bytes, 0, 32);
            System.arraycopy(s, 0, bytes, 32, 32);
            return bytes;
        } catch (Exception e) {
            throw new SMCryptKYException(ErrorCode.E10501, e);
        }
    }

    /**
     * 生成公钥私钥对
     * @return
     */
    public Map<String, byte[]> CMBSM2KeyGen() {
        ECDomainParameters domainParameters = this.getECDomainParameters();
        ECKeyPairGenerator generator = new ECKeyPairGenerator();
        ECKeyGenerationParameters parameters = new ECKeyGenerationParameters(domainParameters, new SecureRandom());
        generator.init(parameters);
        AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) keyPair.getPublic();
        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) keyPair.getPrivate();
        Map<String, byte[]> map = new HashMap();
        map.put("publickey", publicKeyParameters.getQ().getEncoded(false));
        map.put("privatekey", this.format(privateKeyParameters.getD().toByteArray()));
        return map;
    }

    /**
     * 私钥加签
     * @param privkey 私钥
     * @param msg 消息明文
     * @return
     */
    public byte[] CMBSM2SignWithSM3(byte[] privkey, byte[] msg) {
        if (privkey == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg.length == 0) {
            throw new SMCryptKYException(ErrorCode.E10415);
        } else if (privkey.length != 32) {
            throw new SMCryptKYException(ErrorCode.E10418);
        } else {
            ECPrivateKeyParameters privateKey = this.encodePrivateKey(privkey);
            SM2Signer signer = new SM2Signer();
            ParametersWithID parameters = new ParametersWithID(privateKey, USER_ID);
            signer.init(true, parameters);
            signer.update(msg, 0, msg.length);

            try {
                byte[] signature = this.decodeDERSignature(signer.generateSignature());
                return signature;
            } catch (CryptoException e) {
                throw new SMCryptKYException(ErrorCode.E10202, e);
            }
        }
    }

    /**
     * 公钥验签
     * @param pubkey 公钥
     * @param msg 消息明文
     * @param signature 签名
     * @return 0 验签成功， 其他失败
     */
    public int CMBSM2VerifyWithSM3(byte[] pubkey, byte[] msg, byte[] signature) {
        if (pubkey == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (signature == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg.length == 0) {
            throw new SMCryptKYException(ErrorCode.E10415);
        } else if (pubkey.length != 65) {
            throw new SMCryptKYException(ErrorCode.E10417);
        } else if (pubkey[0] != 4) {
            throw new SMCryptKYException(ErrorCode.E10403);
        } else if (signature.length != 64) {
            throw new SMCryptKYException(ErrorCode.E10405);
        } else {
            ECPublicKeyParameters publicKey = this.encodePublicKey(pubkey);
            SM2Signer signer = new SM2Signer();
            ParametersWithID parameters = new ParametersWithID(publicKey, USER_ID);
            signer.init(false, parameters);
            signer.update(msg, 0, msg.length);
            if (!signer.verifySignature(this.encodeDERSignature(signature))) {
                throw new SMCryptKYException(ErrorCode.E10203, "It’s just a failure to check, not an exception.");
            } else {
                return 0;
            }
        }
    }

    /**
     * SM2公钥加密
     * @param pubkey
     * @param msg 消息明文
     * @return
     */
    public byte[] CMBSM2Encrypt(byte[] pubkey, byte[] msg) {
        if (pubkey == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg.length == 0) {
            throw new SMCryptKYException(ErrorCode.E10415);
        } else if (pubkey.length != 65) {
            throw new SMCryptKYException(ErrorCode.E10417);
        } else if (pubkey[0] != 4) {
            throw new SMCryptKYException(ErrorCode.E10403);
        } else {
            ECPublicKeyParameters publicKey = null;

            try {
                publicKey = this.encodePublicKey(pubkey);
            } catch (Exception var7) {
                throw new SMCryptKYException(ErrorCode.E10416);
            }

            SM2Engine engine = new SM2Engine();
            engine.init(true, new ParametersWithRandom(publicKey, new SecureRandom()));

            try {
                byte[] cipherText = engine.processBlock(msg, 0, msg.length);
                return this.C1C2C3ToC1C3C2(cipherText);
            } catch (InvalidCipherTextException e) {
                throw new SMCryptKYException(ErrorCode.E10200, e);
            }
        }
    }

    /**
     * SM2私钥解密
     * @param privkey
     * @param msg  消息明文
     * @return
     */
    public byte[] CMBSM2Decrypt(byte[] privkey, byte[] msg) {
        if (privkey == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (privkey.length != 32) {
            throw new SMCryptKYException(ErrorCode.E10418);
        } else if (msg == null) {
            throw new SMCryptKYException(ErrorCode.E10400);
        } else if (msg.length < 97) {
            throw new SMCryptKYException(ErrorCode.E10419);
        } else if (msg[0] != 4) {
            throw new SMCryptKYException(ErrorCode.E10424);
        } else {
            msg = this.C1C3C2ToC1C2C3(msg);
            ECPrivateKeyParameters privateKey = null;

            try {
                privateKey = this.encodePrivateKey(privkey);
            } catch (Exception e) {
                throw new SMCryptKYException(ErrorCode.E10421);
            }

            SM2Engine engine = new SM2Engine();
            engine.init(false, privateKey);

            try {
                return engine.processBlock(msg, 0, msg.length);
            } catch (InvalidCipherTextException e) {
                throw new SMCryptKYException(ErrorCode.E10201, e);
            }
        }
    }

    /**
     * SM2加签
     * @param data  json字符串
     * @param privateKey  私钥base64字符串
     * @return
     */
    public static String sign(String data, String privateKey) {
        byte[] bytes = SM2.getInstance().CMBSM2SignWithSM3(Base64.getDecoder().decode(privateKey), data.getBytes());
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * SM2验签
     * @param data      json字符串
     * @param signature base64编码签名后数据
     * @param publicKey 公钥base64字符串
     * @return
     */
    public static boolean verifySign(String data, String signature, String publicKey) {
        int ret = -1;
        ret = SM2.getInstance().CMBSM2VerifyWithSM3(Base64.getDecoder().decode(publicKey), data.getBytes(), Base64.getDecoder().decode(signature));
        return ret == 0 ? true : false;
    }

    public static void main(String[] args) {
        SM2 sm2 = SM2.getInstance();
        Map<String, byte[]> pairMap = sm2.CMBSM2KeyGen();
        pairMap.forEach((k, v) -> {
//            System.out.println(k + ":" + HexUtils.toHexString(v));
            System.out.println(k + ":" + Base64Utils.encodeToString(v));
        });

//        String publicKey= "04aabda53043e8dcb86d42f690b61a4db869821dadf9f851ec3c5c43d0c8f95a6677fdba984afc3bb010a8436b1d17cefc2011a34e01e9e801124d29ffa928d803";
//        String privateKey= "4cf170068e9c47ebdb521fb9fc62c4a55a5773fb9da33b0acf8129e28d09d205";
//
//        byte[] bytes = sm2.CMBSM2Encrypt(HexUtils.fromHexString(publicKey), "I Love You Every Day".getBytes());
//        System.out.println(HexUtils.toHexString(bytes));
//
//        String encryptStr ="046a3de2c889be30c99ecf6a317fc251454d4ef3a56f336c9c25f40e57fb870194ed737b377eac306c82eecc445bef45fb73a3ce8af9466275d2c3281d08cd6a7f72f33fc7cf32a98534f68374b0dcd45ae269bac0c3e964bc83cd7a06080d699ccfebaeacb75803bde197d863be74ea079e87c564";
//        byte[] bytes1 = sm2.CMBSM2Decrypt(HexUtils.fromHexString(privateKey), HexUtils.fromHexString(encryptStr));
//        System.out.println(new String(bytes1));
//
//        byte[] signBytes = sm2.CMBSM2SignWithSM3(HexUtils.fromHexString(privateKey), "I Love You Every Day".getBytes());
//        int i = sm2.CMBSM2VerifyWithSM3(HexUtils.fromHexString(publicKey), "I Love You Every Day".getBytes(), signBytes);
//        System.out.println(i);

        String publicKey= "BH0iYS+VchUm6ABSF/cS/PknJgxL16iCBTknevDRXH8Wr49Yc2/WWVvSNxmaJtjjC3aKkSPfOFy48FTJ/YH/ehg=";
        String privateKey= "zuHaOuvY3q5UFCs+LNdAyPhCb0LHGRgoPaQHhR+aO8o=";
        //加密
        byte[] bytes = sm2.CMBSM2Encrypt(Base64Utils.decodeFromString(publicKey), "18774523396".getBytes());
        System.out.println(Base64Utils.encodeToString(bytes));
        //解密
        String base64EncryptStr = "BDjFRn2/ky11DB8+H7VNn1GjpGx9lIa4oSaRusjU+SqkBQVxfCOhXEez9LVyMFCWu+WThkZH81slUqWwGe5wELWuZcZ2jTtIcnfQEwU1mLqI1hCazuLD+NpA+OGbZNDVhDA1RtYt3W070jY/";
        byte[] bytes1 = sm2.CMBSM2Decrypt(Base64Utils.decodeFromString(privateKey), Base64Utils.decodeFromString(base64EncryptStr));
        System.out.println(new String(bytes1));

        String plntxt = "tKnAst75nCdrfI/JgWlt0gjrYrtIZl9d57M6UEKMI0Ybf7wkOw1WuASqPlk76wlwOpkPmhpyHbdtAHXl0w/nRhottGxAUSPN56O+oe4O7mXg6ZwPauncyBpO/J3cm5LiZ/c2RFJ0aGPYnEDu/UwygZ8G9Q1gF7mOH+edyvcEORV7TH8+Eouv49KJ3Kv4cTUSUiso6HGVcW6sKarXxZldDpWHjzz7qkr9TIj/td/UJLIanY7652dZwaMB4wMWrooHF+WcSV3nFD/SjGTudGLLGJq85DU5mPvYzxYMdsqDdM37EJVO/t15Ja+5O/tl6mSyIFKvAQ+NH8/IuT6IxalZqnU5mj9Coc925FURu9+7lxt+l1hl5nhJy3NMxIRBwLjUhbV+W+3j2l+YooppgUnoiGFuHZheMo884NGGimldHqMtLzmvrMRERi3oqWOFfT100B5ogbDZYYbcBF0vUsw605pBQHGj6pP25U4gNay6A/oWcZjmQ01PcDlM2nBNr9OoBSx4ToPAB6ahYJ1svOqxgJEVS4LYcvquu43cyNDpBpktMwYFuvEYW+OI2A/s7Kp+sNnzYkvT6rZ/4NDqSknHQm506eHgoL1KmSz/oFmm7JNXbYGnyuPNQK298FmtkyOWpX3ZHkrW6j9+/i2irTZQuwTQMsF+CMqKlR4CQbMbKdTEv1UAEUa1EjafBaq7HjPj6jP7MQk4HJwMcVXr5JrbQVnBrRRC0DIlM8CptUHCG+W/iZH/MjhIZjLlWF6TGWVnA3ymV144eTh4wQIARjUIRA==";
        String s = SM4Util.sm4CBCDecrypt("cmbtest1cmbtest1", plntxt);
        JSONObject jsonObject = JSON.parseObject(s, Feature.OrderedField);
        String dataStr = jsonObject.getJSONObject("data").toJSONString();
        //加签
        String sign = sign(dataStr, privateKey);
        System.out.println(sign);

        //验签
        String verifyStr = jsonObject.getString("verify");
        System.out.println(verifySign(dataStr, sign, publicKey));

    }

}
	
	
	
	
	
	
	
	
	
	
	
	
	
	